package org.benetech.beans;

import java.util.List;

import org.benetech.collections.Tree.ListDirection;
import org.benetech.util.XMLTreeUtils;
import org.jdom.Element;

/**
 * Filter that specifies that elements are in a given position in their document, as specified by constructor
 * parameters. Note that this filter is not particularly efficient, and so should definitely not be used to refine
 * an entire DOM, but rather to refine previous results. It assumes all elements passed in during the lifetime of this
 * filter belong to the same document, to try to retain some efficiency; also assumes elements passed together into
 * accept are in document order. If elements are not found, they won't be accepted.
 * There is no callback functionality in place.
 * @author Reuben Firmin
 */
public class PositionInDocumentFilter implements Filter<Element, Object> {

	/**
	 * Position of the element.
	 */
	public enum Position {
		/**
		 * Element must be at percentage1/index1 through the document, +/- percentage2/index2
		 */
		APPROXIMATE,
		/**
		 * Element must be at percentage1/index1 in the document
		 */
		EXACT,
		/**
		 * Element must be before percentage1/index1 in the document
		 */
		BEFORE,
		/**
		 * Element must be after percentage1/index1 in the document
		 */
		AFTER,
		/**
		 * Element must be between percentage1/index1 and percentage2/index2 in the document
		 */
		BETWEEN;
	}

	private Position position;
	private Double[] percentages;
	private List<Element> document;
	private Integer[] absoluteIndices;

	/**
	 * Specify the position that elements must be in order to be accepted. This is particularly inefficient.
	 * @param position The position; see the javadoc for meaning of the percentages. You must pass in the correct
	 * number; otherwise ArrayOutOfBounds exceptions will occur.
	 * @param percentage The percentages which narrow it down
	 */
	public PositionInDocumentFilter(Position position, Double... percentages) {
		this.position = position;
		this.percentages = percentages;
	}

	/**
	 * Specify the position that elements must be in order to be accepted. Specify the list of elements that make
	 * up the document.
	 * @param position The position; see the javadoc for meaning of the percentages. You must pass in the correct
	 * number; otherwise ArrayOutOfBounds exceptions will occur.
	 * @param document The list
	 * @param percentage The percentages which narrow it down
	 */
	public PositionInDocumentFilter(Position position, List<Element> document, Double... percentages) {
		this.position = position;
		this.percentages = percentages;
		this.document = document;
	}

	/**
	 * Works with absolute indices instead of percentages. See javadoc of positions of how the indices should be
	 * specified. Specify the list of elements that make up the document.
	 * @param position The position; see the javadoc for meaning of the percentages. You must pass in the correct
	 * number; otherwise ArrayOutOfBounds exceptions will occur.
	 * @param document The list
	 * @param absoluteIndices The indices
	 */
	public PositionInDocumentFilter(Position position, List<Element> document, Integer... absoluteIndices) {
		this.position = position;
		this.document = document;
		this.absoluteIndices = absoluteIndices;
	}

	/**
	 * {@inheritDoc}
	 */
	public boolean accept(final Element... elements) {
		if (elements.length > 0) {
			if (document == null) {
				document = XMLTreeUtils.getDocumentAsTree(elements[0].getDocument(), null)
										.toContentList(ListDirection.DEPTH);
			}
		}
		for (Element element : elements) {
			// iterate from
			int index1;
			// iterate to
			int index2;
			switch (position) {
				case APPROXIMATE :
					if (percentages == null || percentages.length == 0) {
						index1 = absoluteIndices[0] - absoluteIndices[1];
						index2 = absoluteIndices[0] + absoluteIndices[1];
					} else {
						index1 = (int) (((double) document.size() / 100) * (percentages[0] - percentages[1]));
						index2 = (int) (((double) document.size() / 100) * (percentages[0] + percentages[1]));
					}
					break;
				case EXACT :
					if (percentages == null || percentages.length == 0) {
						index1 = absoluteIndices[0];
						index2 = absoluteIndices[0];
					} else {
						index1 = (int) (((double) document.size() / 100) * percentages[0]);
						index2 = (int) (((double) document.size() / 100) * percentages[0]);
					}
					break;
				case BEFORE :
					if (percentages == null || percentages.length == 0) {
						index1 = 0;
						index2 = absoluteIndices[0];
					} else {
						index1 = 0;
						index2 = (int) (((double) document.size() / 100) * percentages[0]);
					}
					break;
				case AFTER :
					if (percentages == null || percentages.length == 0) {
						index1 = absoluteIndices[0];
						index2 = document.size() - 1;
					} else {
						index1 = (int) (((double) document.size() / 100) * percentages[0]);
						index2 = document.size() - 1;
					}
					break;
				case BETWEEN :
					if (percentages == null || percentages.length == 0) {
						index1 = absoluteIndices[0];
						index2 = absoluteIndices[1];
					} else {
						index1 = (int) (((double)document.size() / 100) * percentages[0]);
						index2 = (int) (((double)document.size() / 100) * percentages[1]);
					}
					break;
				default :
					throw new RuntimeException("Missing definition for: " + position);
			}
			boolean found = false;
			if (index1 < 0) {
				index1 = 0;
			}
			if (index2 > document.size() - 1) {
				index2 = document.size() - 1;
			}
			int elIndex = document.indexOf(element);
			if (!(elIndex >= index1 && elIndex <= index2)) {
				return false;
			}
 		}
		return true;
    }

	/**
	 * {@inheritDoc}
	 */
	public boolean acceptWithCallback(FilterCallback<Object> callback, Element... item) {
	    return accept(item);
    }
}
